<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_14</span></h2>

  <p>。</p>

  <p>在Java里，违例控制的目的是使用尽可能精简的代码创建大型、可靠的应用程序，同时排除程序里那些不能控制的错误。</p>

  <p>违例的概念很难掌握。但只有很好地运用它，才可使自己的项目立即获得显著的收益。Java强迫遵守违例所有方面的问题，所以无论库设计者还是客户程序员，都能够连续一致地使用它。</p>

  <p>9.10 练习</p>

  <p>(1) 用main()创建一个类，令其掷出try块内的Exception类的一个对象。为Exception的构建器赋予一个字串参数。在catch从句内捕获违例，并打印出字串参数。添加一个finally从句，并打印一条消息，证明自己真正到达那里。</p>

  <p>(2) 用extends关键字创建自己的违例类。为这个类写一个构建器，令其采用String参数，并随同String句柄把它保存到对象内。写一个方法，令其打印出保存下来的String。创建一个try-catch从句，练习实际操作新违例。</p>

  <p>(3) 写一个类，并令一个方法掷出在练习2中创建的类型的一个违例。试着在没有违例规范的前提下编译它，观察编译器会报告什么。接着添加适当的违例规范。在一个try-catch从句中尝试自己的类以及它的违例。</p>

  <p>(4) 在第5章，找到调用了Assert.java的两个程序，并修改它们，令其掷出自己的违例类型，而不是打印到System.err。该违例应是扩展了RuntimeException的一个内部类。英文版主页 | 中文版主页 | 详细目录 | 关于译者</p>

  <p>-------------------------------------------------</p>

  <p>TXT书库 www.16txt.com</p>

  <p>-------------------------------------------------第10章 Java IO系统</p>

  <p>“对语言设计人员来说，创建好的输入／输出系统是一项特别困难的任务。”</p>

  <p>由于存在大量不同的设计方案，所以该任务的困难性是很容易证明的。其中最大的挑战似乎是如何覆盖所有可能的因素。不仅有三种不同的种类的IO需要考虑（文件、控制台、网络连接），而且需要通过大量不同的方式与它们通信（顺序、随机访问、二进制、字符、按行、按字等等）。</p>

  <p>Java库的设计者通过创建大量类来攻克这个难题。事实上，Java的IO系统采用了如此多的类，以致刚开始会产生不知从何处入手的感觉（具有讽刺意味的是，Java的IO设计初衷实际要求避免过多的类）。从Java 1.0升级到Java 1.1后，IO库的设计也发生了显著的变化。此时并非简单地用新库替换旧库，Sun的设计人员对原来的库进行了大手笔的扩展，添加了大量新的内容。因此，我们有时不得不混合使用新库与旧库，产生令人无奈的复杂代码。</p>

  <p>本章将帮助大家理解标准Java库内的各种IO类，并学习如何使用它们。本章的第一部分将介绍“旧”的Java 1.0 IO流库，因为现在有大量代码仍在使用那个库。本章剩下的部分将为大家引入Java 1.1 IO库的一些新特性。注意若用Java 1.1编译器来编译本章第一部分介绍的部分代码，可能会得到一条“不建议使用该特性”（Deprecated feature）警告消息。代码仍然能够使用；编译器只是建议我们换用本章后面要讲述的一些新特性。但我们这样做是有价值的，因为可以更清楚地认识老方法与新方法之间的一些差异，从而加深我们的理解（并可顺利阅读为Java 1.0写的代码）。</p>

  <p>10.1 输入和输出</p>

  <p>可将Java库的IO类分割为输入与输出两个部分，这一点在用Web浏览器阅读联机Java类文档时便可知道。通过继承，从InputStream（输入流）衍生的所有类都拥有名为read()的基本方法，用于读取单个字节或者字节数组。类似地，从OutputStream衍生的所有类都拥有基本方法write()，用于写入单个字节或者字节数组。然而，我们通常不会用到这些方法；它们之所以存在，是因为更复杂的类可以利用它们，以便提供一个更有用的接口。因此，我们很少用单个类创建自己的系统对象。一般情况下，我们都是将多个对象重叠在一起，提供自己期望的功能。我们之所以感到Java的流库（Stream Library）异常复杂，正是由于为了创建单独一个结果流，却需要创建多个对象的缘故。</p>

  <p>很有必要按照功能对类进行分类。库的设计者首先决定与输入有关的所有类都从InputStream继承，而与输出有关的所有类都从OutputStream继承。</p>

  <p>10.1.1 InputStream的类型</p>

  <p>InputStream的作用是标志那些从不同起源地产生输入的类。这些起源地包括（每个都有一个相关的InputStream子类）：</p>

  <p>(1) 字节数组</p>

  <p>(2) String对象</p>

  <p>(3) 文件</p>

  <p>(4) “管道”，它的工作原理与现实生活中的管道类似：将一些东西置入一端，它们在另一端出来。 (5) 一系列其他流，以便我们将其统一收集到单独一个流内。</p>

  <p>(6) 其他起源地，如Internet连接等（将在本书后面的部分讲述）。</p>

  <p>除此以外，FilterInputStream也属于InputStream的一种类型，用它可为“破坏器”类提供一个基础类，以便将属性或者有用的接口同输入流连接到一起。这将在以后讨论。</p>

  <p>Class</p>

  <p>Function</p>

  <p>Constructor Arguments</p>

  <p>How to use it</p>

  <p>ByteArray-InputStream</p>

  <p>Allows a buffer in memory to be used as an InputStream.</p>

  <p>The buffer from which to extract the bytes.</p>

  <p>As a source of data. Connect it to a FilterInputStream object to provide a useful interface.</p>

  <p>StringBuffer-InputStream</p>

  <p>Converts a String into an InputStream.</p>

  <p>A String. The underlying implementation actually uses a StringBuffer.</p>

  <p>As a source of data. Connect it to a FilterInputStream object to provide a useful interface.</p>

  <p>File-InputStream</p>

  <p>For reading information from a file.</p>

  <p>A String representing the file name, or a File or FileDescriptor object.</p>

  <p>As a source of data. Connect it to a FilterInputStream object to provide a useful interface.类 功能 构建器参数／如何使用</p>

  <p>ByteArrayInputStream 允许内存中的一个缓冲区作为InputStream使用 从中提取字节的缓冲区／作为一个数据源使用。通过将其同一个FilterInputStream对象连接，可提供一个有用的接口</p>

  <p>StringBufferInputStream 将一个String转换成InputStream 一个String（字串）。基础的实施方案实际采用一个StringBuffer（字串缓冲）／作为一个数据源使用。通过将其同一个FilterInputStream对象连接，可提供一个有用的接口</p>

  <p>FileInputStream 用于从文件读取信息 代表文件名的一个String，或者一个File或FileDescriptor对象／作为一个数据源使用。通过将其同一个FilterInputStream对象连接，可提供一个有用的接口</p>

  <p>Piped-InputStream</p>

  <p>Produces the data that’s being written to the associated PipedOutput-Stream. Implements the “piping” concept.</p>

  <p>PipedOutputStream</p>

  <p>As a source of data in multithreading. Connect it to a FilterInputStream object to provide a useful interface.</p>

  <p>Sequence-InputStream</p>

  <p>Coverts two or more InputStream objects into a single InputStream.</p>

  <p>Two InputStream objects or an Enumeration for a container of InputStream objects.</p>

  <p>As a source of data. Connect it to a FilterInputStream object to provide a useful interface.</p>

  <p>Filter-InputStream</p>

  <p>Abstract class which is an interface for decorators that provide useful functionality to the other InputStream classes. See Table 10-3.</p>

  <p>See Table 10-3.</p>

  <p>See Table 10-3.PipedInputString 产生为相关的PipedOutputStream写的数据。实现了“管道化”的概念 PipedOutputStream／作为一个数据源使用。通过将其同一个FilterInputStream对象连接，可提供一个有用的接口</p>

  <p>SequenceInputStream 将两个或更多的InputStream对象转换成单个InputStream使用 两个InputStream对象或者一个Enumeration，用于InputStream对象的一个容器／作为一个数据源使用。通过将其同一个FilterInputStream对象连接，可提供一个有用的接口</p>

  <p>FilterInputStream 对作为破坏器接口使用的类进行抽象；那个破坏器为其他InputStream类提供了有用的功能。参见表10.3 参见表10.3／参见表10.3</p>

  <p>10.1.2 OutputStream的类型</p>

  <p>这一类别包括的类决定了我们的输入往何处去：一个字节数组（但没有String；假定我们可用字节数组创建一个）；一个文件；或者一个“管道”。</p>

  <p>除此以外，FilterOutputStream为“破坏器”类提供了一个基础类，它将属性或者有用的接口同输出流连接起来。这将在以后讨论。</p>

  <p>表10.2 OutputStream的类型</p>

  <p>Class</p>

  <p>Function</p>

  <p>Constructor Arguments</p>

  <p>How to use it</p>

  <p>ByteArray-OutputStream</p>

  <p>Creates a buffer in memory. All the data that you send to the stream is placed in this buffer.</p>

  <p>Optional initial size of the buffer.</p>

  <p>To designate the destination of your data. Connect it to a FilterOutputStream object to provide a useful interface.</p>

  <p>File-OutputStream</p>

  <p>For sending information to a file.</p>

  <p>A String representing the file name, or a File or FileDescriptor object.</p>

  <p>To designate the destination of your data. Connect it to a FilterOutputStream object to provide a useful interface.</p>

  <p>Piped-OutputStream</p>

  <p>Any information you write to this automatically ends up as input for the associated PipedInput-Stream. Implements the “piping” concept.</p>

  <p>PipedInputStream</p>

  <p>To designate the destination of your data for multithreading. Connect it to a FilterOutputStream object to provide a useful interface.</p>

  <p>Filter-OutputStream</p>

  <p>Abstract class which is an interface for decorators that provide useful functionality to the other OutputStream classes. See Table</p>

  <p>10-4.</p>

  <p>See Table 10-4.</p>

  <p>See Table 10-4.类 功能 构建器参数／如何使用</p>

  <p>ByteArrayOutputStream 在内存中创建一个缓冲区。我们发送给流的所有数据都会置入这个缓冲区。 可选缓冲区的初始大小／用于指出数据的目的地。若将其同FilterOutputStream对象连接到一起，可提供一个有用的接口</p>

  <p>FileOutputStream 将信息发给一个文件 用一个String代表文件名，或选用一个File或FileDescriptor对象／用于指出数据的目的地。若将其同FilterOutputStream对象连接到一起，可提供一个有用的接口</p>

  <p>PipedOutputStream 我们写给它的任何信息都会自动成为相关的PipedInputStream的输出。实现了“管道化”的概念 PipedInputStream／为多线程处理指出自己数据的目的地／将其同FilterOutputStream对象连接到一起，便可提供一个有用的接口</p>

  <p>FilterOutputStream 对作为破坏器接口使用的类进行抽象处理；那个破坏器为其他OutputStream类提供了有用的功能。参见表10.4 参见表10.4／参见表10.4</p>

  <p>10.2 增添属性和有用的接口</p>

  <p>利用层次化对象动态和透明地添加单个对象的能力的做法叫作“装饰器”（Decorator）方案――“方案”属于本书第16章的主题（注释①）。装饰器方案规定封装于初始化对象中的所有对象都拥有相同的接口，以便利用装饰器的“透明”性质――我们将相同的消息发给一个对象，无论它是否已被“装饰”。这正是在Java IO库里存在“过滤器”（Filter）类的原因：抽象的“过滤器”类是所有装饰器的基础类（装饰器必须拥有与它装饰的那个对象相同的接口，但装饰器亦可对接口作出扩展，这种情况见诸于几个特殊的“过滤器”类中）。</p>

  <p>子类处理要求大量子类对每种可能的组合提供支持时，便经常会用到装饰器――由于组合形式太多，造成子类处理变得不切实际。Java IO库要求许多不同的特性组合方案，这正是装饰器方案显得特别有用的原因。但是，装饰器方案也有自己的一个缺点。在我们写一个程序的时候，装饰器为我们提供了大得多的灵活性（因为可以方便地混合与匹配属性），但它们也使自己的代码变得更加复杂。原因在于Java IO库操作不便，我们必须创建许多类――“核心”IO类型加上所有装饰器――才能得到自己希望的单个IO对象。</p>

  <p>FilterInputStream和FilterOutputStream（这两个名字不十分直观）提供了相应的装饰器接口，用于控制一个特定的输入流（InputStream）或者输出流（OutputStream）。它们分别是从InputStream和OutputStream衍生出来的。此外，它们都属于抽象类，在理论上为我们与一个流的不同通信手段都提供了一个通用的接口。事实上，FilterInputStream和FilterOutputStream只是简单地模仿了自己的基础类，它们是一个装饰器的基本要求。</p>

  <p>10.2.1 通过FilterInputStream从InputStream里读入数据</p>

  <p>FilterInputStream类要完成两件全然不同的事情。其中，DataInputStream允许我们读取不同的基本类型数据以及String对象（所有方法都以“read”开头，比如readByte()，readFloat()等等）。伴随对应的DataOutputStream，我们可通过数据“流”将基本类型的数据从一个地方搬到另一个地方。这些“地方”是由表10.1总结的那些类决定的。若读取块内的数据，并自己进行解析，就不需要用到DataInputStream。但在其他许多情况下，我们一般都想用它对自己读入的数据进行自动格式化。</p>

  <p>剩下的类用于修改InputStream的内部行为方式：是否进行缓冲，是否跟踪自己读入的数据行，以及是否能够推回一个字符等等。后两种类看起来特别象提供对构建一个编译器的支持（换言之，添加它们为了支持Java编译器的构建），所以在常规编程中一般都用不着它们。</p>

  <p>也许几乎每次都要缓冲自己的输入，无论连接的是哪个IO设备。所以IO库最明智的做法就是将未缓冲输入作为一种特殊情况处理，同时将缓冲输入接纳为标准做法。</p>

  <p>表10.3 FilterInputStream的类型</p>

  <p>Class</p>

  <p>Function</p>

  <p>Constructor Arguments</p>

  <p>How to use it</p>

  <p>Data-InputStream</p>

  <p>Used in concert with DataOutputStream, so you can read primitives (int, char, long, etc.) from a stream in a portable fashion.</p>

  <p>InputStream</p>

  <p>Contains a full interface to allow you to read primitive types.</p>

  <p>Buffered-InputStream</p>

  <p>Use this to prevent a physical read every time you want more data. You’re saying “Use a buffer.”</p>

  <p>InputStream, with optional buffer size.</p>

  <p>This doesn’t provide an interface per se, just a requirement that a buffer be used. Attach an interface object.</p>

  <p>LineNumber-InputStream</p>

  <p>Keeps track of line numbers in the input stream; you can call getLineNumber( ) and setLineNumber(int).</p>

  <p>InputStream</p>

  <p>This just adds line numbering, so you’ll probably attach an interface object.</p>

  <p>Pushback-InputStream</p>

  <p>Has a one byte push-back buffer so that you can push back the last character read.</p>

  <p>InputStream</p>

  <p>Generally used in the scanner for a compiler and probably included because the Java compiler needed it. You probably won’t use this.类 功能 构建器参数／如何使用</p>

  <p>DataInputStream 与DataOutputStream联合使用，使自己能以机动方式读取一个流中的基本数据类型（int，char，long等等） InputStream/包含了一个完整的接口，以便读取基本数据类型</p>

  <p>BufferedInputStream 避免每次想要更多数据时都进行物理性的读取，告诉它“请先在缓冲区里找” InputStream，没有可选的缓冲区大小／本身并不能提供一个接口，只是发出使用缓冲区的要求。要求同一个接口对象连接到一起</p>

  <p>LineNumberInputStream 跟踪输入流中的行号；可调用getLineNumber()以及setLineNumber(int) 只是添加对数据行编号的能力，所以可能需要同一个真正的接口对象连接</p>

  <p>PushbackInputStream 有一个字节的后推缓冲区，以便后推读入的上一个字符 InputStream／通常由编译器在扫描器中使用，因为Java编译器需要它。一般不在自己的代码中使用</p>

  <p>10.2.2 通过FilterOutputStream向OutputStream里写入数据</p>

  <p>与DataInputStream对应的是DataOutputStream，后者对各个基本数据类型以及String对象进行格式化，并将其置入一个数据“流”中，以便任何机器上的DataInputStream都能正常地读取它们。所有方法都以“wirte”开头，例如writeByte()，writeFloat()等等。</p>

  <p>若想进行一些真正的格式化输出，比如输出到控制台，请使用PrintStream。利用它可以打印出所有基本数据类型以及String对象，并可采用一种易于查看的格式。这与DataOutputStream正好相反，后者的目标是将那些数据置入一个数据流中，以便DataInputStream能够方便地重新构造它们。System.out静态对象是一个PrintStream。</p>

  <p>PrintStream内两个重要的方法是print()和println()。它们已进行了覆盖处理，可打印出所有数据类型。print()和println()之间的差异是后者在操作完毕后会自动添加一个新行。</p>

  <p>BufferedOutputStream属于一种“修改器”，用于指示数据流使用缓冲技术，使自己不必每次都向流内物理性地写入数据。通常都应将它应用于文件处理和控制器IO。</p>

  <p>表10.4 FilterOutputStream的类型</p>

  <p>Class</p>

  <p>Function</p>

  <p>Constructor Arguments</p>

  <p>How to use it</p>

  <p>Data-OutputStream</p>

  <p>Used in concert with DataInputStream so you can write primitives (int, char, long, etc.) to a stream in a portable fashion.</p>

  <p>OutputStream</p>

  <p>Contains full interface to allow you to write primitive types.</p>

  <p>PrintStream</p>

  <p>For producing formatted output. While DataOutputStream handles the storage of data, PrintStream handles display.</p>

  <p>OutputStream, with optional boolean indicating that the buffer is flushed with every newline.</p>

  <p>Should be the “final” wrapping for your OutputStream object. You’ll probably use this a lot.</p>

  <p>Buffered-OutputStream</p>

  <p>Use this to prevent a physical write every time you send a piece of data. You’re saying “Use a buffer.” You can call flush( ) to flush the buffer.</p>

  <p>OutputStream, with optional buffer size.</p>

  <p>This doesn’t provide an interface per se, just a requirement that a buffer is used. Attach an interface object.类 功能 构建器参数／如何使用</p>

  <p>DataOutputStream 与DataInputStream配合使用，以便采用方便的形式将基本数据类型（int，char，long等）写入一个数据流 OutputStream／包含了完整接口，以便我们写入基本数据类型</p>

  <p>PrintStream 用于产生格式化输出。DataOutputStream控制的是数据的“存储”，而PrintStream控制的是“显示” OutputStream，可选一个布尔参数，指示缓冲区是否与每个新行一同刷新／对于自己的OutputStream对象，应该用“final”将其封闭在内。可能经常都要用到它</p>

  <p>BufferedOutputStream 用它避免每次发出数据的时候都要进行物理性的写入，要求它“请先在缓冲区里找”。可调用flush()，对缓冲区进行刷新 OutputStream，可选缓冲区大小／本身并不能提供一个接口，只是发出使用缓冲区的要求。需要同一个接口对象连接到一起</p>

  <p>10.3 本身的缺陷：RandomAccessFile</p>

  <p>RandomAccessFile用于包含了已知长度记录的文件，以便我们能用seek()从一条记录移至另一条；然后读取或修改那些记录。各记录的长度并不一定相同；只要知道它们有多大以及置于文件何处即可。</p>

  <p>首先，我们有点难以相信RandomAccessFile不属于InputStream或者OutputStream分层结构的一部分。除了恰巧实现了DataInput以及DataOutput（这两者亦由DataInputStream和DataOutputStream实现）接口之外，它们与那些分层结构并无什么关系。它甚至没有用到现有InputStream或OutputStream类的功能――采用的是一个完全不相干的类。该类属于全新的设计，含有自己的全部（大多数为固有）方法。之所以要这样做，是因为RandomAccessFile拥有与其他IO类型完全不同的行为，因为我们可在一个文件里向前或向后移动。不管在哪种情况下，它都是独立运作的，作为Object的一个“直接继承人”使用。</p>

  <p>从根本上说，RandomAccessFile类似DataInputStream和DataOutputStream的联合使用。其中，getFilePointer()用于了解当前在文件的什么地方，seek()用于移至文件内的一个新地点，而length()用于判断文件的最大长度。此外，构建器要求使用另一个自变量（与C的fopen()完全一样），指出自己只是随机读（"r"），还是读写兼施（"rw"）。这里没有提供对“只写文件”的支持。也就是说，假如是从DataInputStream继承的，那么RandomAccessFile也有可能能很好地工作。</p>

  <p>还有更难对付的。很容易想象我们有时要在其他类型的数据流中搜索，比如一个ByteArrayInputStream，但搜索方法只有RandomAccessFile才会提供。而后者只能针对文件才能操作，不能针对数据流操作。此时，BufferedInputStream确实允许我们标记一个位置（使用mark()，它的值容纳于单个内部变量中），并用reset()重设那个位置。但这些做法都存在限制，并不是特别有用。</p>

  <p>10.4 File类</p>

  <p>File类有一个欺骗性的名字――通常会认为它对付的是一个文件，但实情并非如此。它既代表一个特定文件的名字，也代表目录内一系列文件的名字。若代表一个文件集，便可用list()方法查询这个集，返回的是一个字串数组。之所以要返回一个数组，而非某个灵活的集合类，是因为元素的数量是固定的。而且若想得到一个不同的目录列表，只需创建一个不同的File对象即可。事实上，“FilePath”（文件路径）似乎是一个更好的名字。本节将向大家完整地例示如何使用这个类，其中包括相关的FilenameFilter（文件名过滤器）接口。</p>

  <p>10.4.1 目录列表器</p>

  <p>现在假设我们想观看一个目录列表。可用两种方式列出File对象。若在不含自变量（参数）的情况下调用list()，会获得File对象包含的一个完整列表。然而，若想对这个列表进行某些限制，就需要使用一个“目录过滤器”，该类的作用是指出应如何选择File对象来完成显示。</p>

  <p>下面是用于这个例子的代码（或在执行该程序时遇到困难，请参考第3章3.1.2小节“赋值”）：</p>

  <p>//: DirList.java</p>

  <p>// Displays directory listing</p>

  <p>package c10;</p>

  <p>import java.io.*;</p>

  <p>public class DirList {</p>

  <p>public static void main(String[] args) {</p>

  <p>try {</p>

  <p>File path = new File(".");</p>

  <p>String[] list;</p>

  <p>if(args.length == 0)</p>

  <p>list = path.list();</p>

  <p>else</p>

  <p>list = path.list(new DirFilter(args[0]));</p>

  <p>for(int i = 0; i &lt; list.length; i++)</p>

  <p>System.out.println(list[i]);</p>

  <p>} catch(Exception e) {</p>

  <p>e.printStackTrace();</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class DirFilter implements FilenameFilter {</p>

  <p>String afn;</p>

  <p>DirFilter(String afn) { this.afn = afn; }</p>

  <p>public boolean accept(File dir, String name) {</p>

  <p>// Strip path information:</p>

  <p>String f = new File(name).getName();</p>

  <p>return f.indexOf(afn) != -1;</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>DirFilter类“实现”了interface FilenameFilter（关于接口的问题，已在第7章进行了详述）。下面让我们看看FilenameFilter接口有多么简单：</p>

  <p>public interface FilenameFilter {</p>

  <p>boolean accept(文件目录, 字串名);</p>

  <p>}</p>

  <p>它指出这种类型的所有对象都提供了一个名为accept()的方法。之所以要创建这样的一个类，背后的全部原因就是把accept()方法提供给list()方法，使list()能够“回调”accept()，从而判断应将哪些文件名包括到列表中。因此，通常将这种技术称为“回调”，有时也称为“算子”（也就是说，DirFilter是一个算子，因为它唯一的作用就是容纳一个方法）。由于list()采用一个FilenameFilter对象作为自己的自变量使用，所以我们能传递实现了FilenameFilter的任何类的一个对象，用它决定（甚至在运行期）list()方法的行为方式。回调的目的是在代码的行为上提供更大的灵活性。</p>

  <p>通过DirFilter，我们看出尽管一个“接口”只包含了一系列方法，但并不局限于只能写那些方法（但是，至少必须提供一个接口内所有方法的定义。在这种情况下，DirFilter构建器也会创建）。</p>

  <p>accept()方法必须接纳一个File对象，用它指示用于寻找一个特定文件的目录；并接纳一个String，其中包含了要寻找之文件的名字。可决定使用或忽略这两个参数之一，但有时至少要使用文件名。记住list()方法准备为目录对象中的每个文件名调用accept()，核实哪个应包含在内――具体由accept()返回的“布尔”结果决定。</p>

  <p>为确定我们操作的只是文件名，其中没有包含路径信息，必须采用String对象，并在它的外部创建一个File对象。然后调用getName()，它的作用是去除所有路径信息（采用与平台无关的方式）。随后，accept()用String类的indexOf()方法检查文件名内部是否存在搜索字串"afn"。若在字串内找到afn，那么返回值就是afn的起点索引；但假如没有找到，返回值就是-1。注意这只是一个简单的字串搜索例子，未使用常见的表达式“通配符”方案，比如"fo?.b?r*"；这种方案更难实现。</p>

  <p>list()方法返回的是一个数组。可查询这个数组的长度，然后在其中遍历，选定数组元素。与C和C++的类似行为相比，这种于方法内外方便游历数组的行为无疑是一个显著的进步。</p>

  <p>1. 匿名内部类</p>

  <p>下例用一个匿名内部类（已在第7章讲述）来重写显得非常理想。首先创建了一个filter()方法，它返回指向FilenameFilter的一个句柄：</p>

  <p>//: DirList2.java</p>

  <p>// Uses Java 1.1 anonymous inner classes</p>

  <p>import java.io.*;</p>

  <p>public class DirList2 {</p>

  <p>public static FilenameFilter</p>

  <p>filter(final String afn) {</p>

  <p>// Creation of anonymous inner class:</p>

  <p>return new FilenameFilter() {</p>

  <p>String fn = afn;</p>

  <p>public boolean accept(File dir, String n) {</p>

  <p>// Strip path information:</p>

  <p>String f = new File(n).getName();</p>

  <p>return f.indexOf(fn) != -1;</p>

  <p>}</p>

  <p>}; // End of anonymous inner class</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>try {</p>

  <p>File path = new File(".");</p>

  <p>String[] list;</p>

  <p>if(args.length == 0)</p>

  <p>list = path.list();</p>

  <p>else</p>

  <p>list = path.list(filter(args[0]));</p>

  <p>for(int i = 0; i &lt; list.length; i++)</p>

  <p>System.out.println(list[i]);</p>

  <p>} catch(Exception e) {</p>

  <p>e.printStackTrace();</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>注意filter()的自变量必须是final。这一点是匿名内部类要求的，使其能使用来自本身作用域以外的一个对象。</p>

  <p>之所以认为这样做更好，是由于FilenameFilter类现在同DirList2紧密地结合在一起。然而，我们可采取进一步的操作，将匿名内部类定义成list()的一个参数，使其显得更加精简。如下所示：</p>

  <p>//: DirList3.java</p>

  <p>// Building the anonymous inner class "in-place"</p>

  <p>import java.io.*;</p>

  <p>public class DirList3 {</p>

  <p>public static void main(final String[] args) {</p>

  <p>try {</p>

  <p>File path = new File(".");</p>

  <p>String[] list;</p>

  <p>if(args.length == 0)</p>

  <p>list = path.list();</p>

  <p>else</p>

  <p>list = path.list(</p>

  <p>new FilenameFilter() {</p>

  <p>public boolean</p>

  <p>accept(File dir, String n) {</p>

  <p>String f = new File(n).getName();</p>

  <p>return f.indexOf(args[0]) != -1;</p>

  <p>}</p>

  <p>});</p>

  <p>for(int i = 0; i &lt; list.length; i++)</p>

  <p>System.out.println(list[i]);</p>

  <p>} catch(Exception e) {</p>

  <p>e.printStackTrace();</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>main()现在的自变量是final，因为匿名内部类直接使用args[0]。</p>

  <p>这展示了如何利用匿名内部类快速创建精简的类，以便解决一些复杂的问题。由于Java中的所有东西都与类有关，所以它无疑是一种相当有用的编码技术。它的一个好处是将特定的问题隔离在一个地方统一解决。但在另一方面，这样生成的代码不是十分容易阅读，所以使用时必须慎重。</p>

  <p>2. 顺序目录列表</p>

  <p>经常都需要文件名以排好序的方式提供。由于Java 1.0和Java 1.1都没有提供对排序的支持（从Java 1.2开始提供），所以必须用第8章创建的SortVector将这一能力直接加入自己的程序。就象下面这样：</p>

  <p>//: SortedDirList.java</p>

  <p>// Displays sorted directory listing</p>

  <p>import java.io.*;</p>

  <p>import c08.*;</p>

  <p>public class SortedDirList {</p>

  <p>private File path;</p>

  <p>private String[] list;</p>

  <p>public SortedDirList(final String afn) {</p>

  <p>path = new File(".");</p>

  <p>if(afn == null)</p>

  <p>list = path.list();</p>

  <p>else</p>

  <p>list = path.list(</p>

  <p>new FilenameFilter() {</p>

  <p>public boolean</p>

  <p>accept(File dir, String n) {</p>

  <p>String f = new File(n).getName();</p>

  <p>return f.indexOf(afn) != -1;</p>

  <p>}</p>

  <p>});</p>

  <p>sort();</p>

  <p>}</p>

  <p>void print() {</p>

  <p>for(int i = 0; i &lt; list.length; i++)</p>

  <p>System.out.println(list[i]);</p>

  <p>}</p>

  <p>private void sort() {</p>

  <p>StrSortVector sv = new StrSortVector();</p>

  <p>for(int i = 0; i &lt; list.length; i++)</p>

  <p>sv.addElement(list[i]);</p>

  <p>// The first time an element is pulled from</p>

  <p>// the StrSortVector the list is sorted:</p>

  <p>for(int i = 0; i &lt; list.length; i++)</p>

  <p>list[i] = sv.elementAt(i);</p>

  <p>}</p>

  <p>// Test it:</p>

  <p>public static void main(String[] args) {</p>

  <p>SortedDirList sd;</p>

  <p>if(args.length == 0)</p>

  <p>sd = new SortedDirList(null);</p>

  <p>else</p>

  <p>sd = new SortedDirList(args[0]);</p>

  <p>sd.print();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这里进行了另外少许改进。不再是将path（路径）和list（列表）创建为main()的本地变量，它们变成了类的成员，使它们的值能在对象“生存”期间方便地访问。事实上，main()现在只是对类进行测试的一种方式。大家可以看到，一旦列表创建完毕，类的构建器就会自动开始对列表进行排序。</p>

  <p>这种排序不要求区分大小写，所以最终不会得到一组全部单词都以大写字母开头的列表，跟着是全部以小写字母开头的列表。然而，我们注意到在以相同字母开头的一组文件名中，大写字母是排在前面的――这对标准的排序来说仍是一种不合格的行为。Java 1.2已成功解决了这个问题。</p>

  <p>10.4.2 检查与创建目录</p>

  <p>File类并不仅仅是对现有目录路径、文件或者文件组的一个表示。亦可用一个File对象新建一个目录，甚至创建一个完整的目录路径――假如它尚不存在的话。亦可用它了解文件的属性（长度、上一次修改日期、读／写属性等），检查一个File对象到底代表一个文件还是一个目录，以及删除一个文件等等。下列程序完整展示了如何运用File类剩下的这些方法：</p>

  <p>//: MakeDirectories.java</p>

  <p>// Demonstrates the use of the File class to</p>

  <p>// create directories and manipulate files.</p>

  <p>import java.io.*;</p>

  <p>public class MakeDirectories {</p>

  <p>private final static String usage =</p>

  <p>"Usage:MakeDirectories path1 ...\n" +</p>

  <p>"Creates each path\n" +</p>

  <p>"Usage:MakeDirectories -d path1 ...\n" +</p>

  <p>"Deletes each path\n" +</p>

  <p>"Usage:MakeDirectories -r path1 path2\n" +</p>

  <p>"Renames from path1 to path2\n";</p>

  <p>private static void usage() {</p>

  <p>System.err.println(usage);</p>

  <p>System.exit(1);</p>

  <p>}</p>

  <p>private static void fileData(File f) {</p>

  <p>System.out.println(</p>

  <p>"Absolute path: " + f.getAbsolutePath() +</p>

  <p>"\n Can read: " + f.canRead() +</p>

  <p>"\n Can write: " + f.canWrite() +</p>

  <p>"\n getName: " + f.getName() +</p>

  <p>"\n getParent: " + f.getParent() +</p>

  <p>"\n getPath: " + f.getPath() +</p>

  <p>"\n length: " + f.length() +</p>

  <p>"\n lastModified: " + f.lastModified());</p>

  <p>if(f.isFile())</p>

  <p>System.out.println("it's a file");</p>

  <p>else if(f.isDirectory())</p>

  <p>System.out.println("it's a directory");</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>if(args.length &lt; 1) usage();</p>

  <p>if(args[0].equals("-r")) {</p>

  <p>if(args.length != 3) usage();</p>

  <p>File</p>

  <p>old = new File(args[1]),</p>

  <p>rname = new File(args[2]);</p>

  <p>old.renameTo(rname);</p>

  <p>fileData(old);</p>

  <p>fileData(rname);</p>

  <p>return; // Exit main</p>

  <p>}</p>

  <p>int count = 0;</p>

  <p>boolean del = false;</p>

  <p>if(args[0].equals("-d")) {</p>

  <p>count++;</p>

  <p>del = true;</p>

  <p>}</p>

  <p>for( ; count &lt; args.length; count++) {</p>

  <p>File f = new File(args[count]);</p>

  <p>if(f.exists()) {</p>

  <p>System.out.println(f + " exists");</p>

  <p>if(del) {</p>

  <p>System.out.println("deleting..." + f);</p>

  <p>f.delete();</p>

  <p>}</p>

  <p>}</p>

  <p>else { // Doesn't exist</p>

  <p>if(!del) {</p>

  <p>f.mkdirs();</p>

  <p>System.out.println("created " + f);</p>

  <p>}</p>

  <p>}</p>

  <p>fileData(f);</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在fileData()中，可看到应用了各种文件调查方法来显示与文件或目录路径有关的信息。</p>

  <p>main()应用的第一个方法是renameTo()，利用它可以重命名（或移动）一个文件至一个全新的路径（该路径由参数决定），它属于另一个File对象。这也适用于任何长度的目录。</p>

  <p>若试验上述程序，就可发现自己能制作任意复杂程度的一个目录路径，因为mkdirs()会帮我们完成所有工作。在Java 1.0中，-d标志报告目录虽然已被删除，但它依然存在；但在Java 1.1中，目录会被实际删除。</p>

  <p>10.5 IO流的典型应用</p>

  <p>尽管库内存在大量IO流类，可通过多种不同的方式组合到一起，但实际上只有几种方式才会经常用到。然而，必须小心在意才能得到正确的组合。下面这个相当长的例子展示了典型IO配置的创建与使用，可在写自己的代码时将其作为一个参考使用。注意每个配置都以一个注释形式的编号起头，并提供了适当的解释信息。</p>

  <p>//: IOStreamDemo.java</p>

  <p>// Typical IO Stream Configurations</p>

  <p>import java.io.*;</p>

  <p>import com.bruceeckel.tools.*;</p>

  <p>public class IOStreamDemo {</p>

  <p>public static void main(String[] args) {</p>

  <p>try {</p>

  <p>// 1. Buffered input file</p>

  <p>DataInputStream in =</p>

  <p>new DataInputStream(</p>

  <p>new BufferedInputStream(</p>

  <p>new FileInputStream(args[0])));</p>

  <p>String s, s2 = new String();</p>

  <p>while((s = in.readLine())!= null)</p>

  <p>s2 += s + "\n";</p>

  <p>in.close();</p>

  <p>// 2. Input from memory</p>

  <p>StringBufferInputStream in2 =</p>

  <p>new StringBufferInputStream(s2);</p>

  <p>int c;</p>

  <p>while((c = in2.read()) != -1)</p>

  <p>System.out.print((char)c);</p>

  <p>// 3. Formatted memory input</p>

  <p>try {</p>

  <p>DataInputStream in3 =</p>

  <p>new DataInputStream(</p>

  <p>new StringBufferInputStream(s2));</p>

  <p>while(true)</p>

  <p>System.out.print((char)in3.readByte());</p>

  <p>} catch(EOFException e) {</p>

  <p>System.out.println(</p>

  <p>"End of stream encountered");</p>

  <p>}</p>

  <p>// 4. Line numbering &amp; file output</p>

  <p>try {</p>

  <p>LineNumberInputStream li =</p>

  <p>new LineNumberInputStream(</p>

  <p>new StringBufferInputStream(s2));</p>

  <p>DataInputStream in4 =</p>

  <p>new DataInputStream(li);</p>

  <p>PrintStream out1 =</p>

  <p>new PrintStream(</p>

  <p>new BufferedOutputStream(</p>

  <p>new FileOutputStream(</p>

  <p>"IODemo.out")));</p>

  <p>while((s = in4.readLine()) != null )</p>

  <p>out1.println(</p>

  <p>"Line " + li.getLineNumber() + s);</p>

  <p>out1.close(); // finalize() not reliable!</p>

  <p>} catch(EOFException e) {</p>

  <p>System.out.println(</p>

  <p>"End of stream encountered");</p>

  <p>}</p>

  <p>// 5. Storing &amp; recovering data</p>

  <p>try {</p>

  <p>DataOutputStream out2 =</p>

  <p>new DataOutputStream(</p>

  <p>new BufferedOutputStream(</p>

  <p>new FileOutputStream("Data.txt")));</p>

  <p>out2.writeBytes(</p>

  <p>"Here's the value of pi: \n");</p>

  <p>out2.writeDouble(3.14159);</p>

  <p>out2.close();</p>

  <p>DataInputStream in5 =</p>

  <p>new DataInputStream(</p>

  <p>new BufferedInputStream(</p>

  <p>new FileInputStream("Data.txt")));</p>

  <p>System.out.println(in5.readLine());</p>

  <p>System.out.println(in5.readDouble());</p>

  <p>} catch(EOFException e) {</p>

  <p>System.out.println(</p>

  <p>"End of stream encountered");</p>

  <p>}</p>

  <p>// 6. Reading/writing random access files</p>

  <p>RandomAccessFile rf =</p>

  <p>new RandomAccessFile("rtest.dat", "rw");</p>

  <p>for(int i = 0; i &lt; 10; i++)</p>

  <p>rf.writeDouble(i*1.414);</p>

  <p>rf.close();</p>

  <p>rf =</p>

  <p>new RandomAccessFile("rtest.dat", "rw");</p>

  <p>rf.seek(5*8);</p>

  <p>rf.writeDouble(47.0001);</p>

  <p>rf.close();</p>

  <p>rf =</p>

  <p>new RandomAccessFile("rtest.dat", "r");</p>

  <p>for(int i = 0; i &lt; 10; i++)</p>

  <p>System.out.println(</p>

  <p>"Value " + i + ": " +</p>

  <p>rf.readDouble());</p>

  <p>rf.close();</p>

  <p>// 7. File input shorthand</p>

  <p>InFile in6 = new InFile(args[0]);</p>

  <p>String s3 = new String();</p>

  <p>System.out.println(</p>

  <p>"First line in file: " +</p>

  <p>in6.readLine());</p>

  <p>in6.close();</p>

  <p>// 8. Formatted file output shorthand</p>

  <p>PrintFile out3 = new PrintFile("Data2.txt");</p>

  <p>out3.print("Test of PrintFile");</p>

  <p>out3.close();</p>

  <p>// 9. Data file output shorthand</p>

  <p>OutFile out4 = new OutFile("Data3.txt");</p>

  <p>out4.writeBytes("Test of outDataFile\n\r");</p>

  <p>out4.writeChars("Test of outDataFile\n\r");</p>

  <p>out4.close();</p>

  <p>} catch(FileNotFoundException e) {</p>

  <p>System.out.println(</p>

  <p>"File Not Found:" + args[0]);</p>

  <p>} catch(IOException e) {</p>

  <p>System.out.println("IO Exception");</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>10.5.1 输入流</p>

  <p>当然，我们经常想做的一件事情是将格式化的输出打印到控制台，但那已在第5章创建的com.bruceeckel.tools中得到了简化。</p>

  <p>第1到第4部分演示了输入流的创建与使用（尽管第4部分展示了将输出流作为一个测试工具的简单应用）。</p>

  <p>1. 缓冲的输入文件</p>

  <p>为打开一个文件以便输入，需要使用一个FileInputStream，同时将一个String或File对象作为文件名使用。为提高速度，最好先对文件进行缓冲处理，从而获得用于一个BufferedInputStream的构建器的结果句柄。为了以格式化的形式读取输入数据，我们将那个结果句柄赋给用于一个DataInputStream的构建器。DataInputStream是我们的最终（final）对象，并是我们进行读取操作的接口。</p>

  <p>在这个例子中，只用到了readLine()方法，但理所当然任何DataInputStream方法都可以采用。一旦抵达文件末尾，readLine()就会返回一个null（空），以便中止并退出while循环。</p>

  <p>“String s2”用于聚集完整的文件内容（包括必须添加的新行，因为readLine()去除了那些行）。随后，在本程序的后面部分中使用s2。最后，我们调用close()，用它关闭文件。从技术上说，会在运行finalize()时调用close()。而且我们希望一旦程序退出，就发生这种情况（无论是否进行垃圾收集）。然而，Java 1.0有一个非常突出的错误（Bug），造成这种情况不会发生。在Java 1.1中，必须明确调用System.runFinalizersOnExit(true)，用它保证会为系统中的每个对象调用finalize()。然而，最安全的方法还是为文件明确调用close()。</p>

  <p>2. 从内存输入</p>

  <p>这一部分采用已经包含了完整文件内容的String s2，并用它创建一个StringBufferInputStream（字串缓冲输入流）――作为构建器的参数，要求使用一个String，而非一个StringBuffer）。随后，我们用read()依次读取每个字符，并将其发送至控制台。注意read()将下一个字节返回为int，所以必须将其造型为一个char，以便正确地打印。</p>

  <p>3. 格式化内存输入</p>

  <p>StringBufferInputStream的接口是有限的，所以通常需要将其封装到一个DataInputStream内，从而增强它的能力。然而，若选择用readByte()每次读出一个字符，那么所有值都是有效的，所以不可再用返回值来侦测何时结束输入。相反，可用available()方法判断有多少字符可用。下面这个例子展示了如何从文件中一次读出一个字符：</p>

  <p>//: TestEOF.java</p>

  <p>// Testing for the end of file while reading</p>

  <p>// a byte at a time.</p>

  <p>import java.io.*;</p>

  <p>public class TestEOF {</p>

  <p>public static void main(String[] args) {</p>

  <p>try {</p>

  <p>DataInputStream in =</p>

  <p>new DataInputStream(</p>

  <p>new BufferedInputStream(</p>

  <p>new FileInputStream("TestEof.java")));</p>

  <p>while(in.available() != 0)</p>

  <p>System.out.print((char)in.readByte());</p>

  <p>} catch (IOException e) {</p>

  <p>System.err.println("IOException");</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>注意取决于当前从什么媒体读入，avaiable()的工作方式也是有所区别的。它在字面上意味着“可以不受阻塞读取的字节数量”。对一个文件来说，它意味着整个文件。但对一个不同种类的数据流来说，它却可能有不同的含义。因此在使用时应考虑周全。</p>

  <p>为了在这样的情况下侦测输入的结束，也可以通过捕获一个违例来实现。然而，若真的用违例来控制数据流，却显得有些大材小用。</p>

  <p>4. 行的编号与文件输出</p>

  <p>这个例子展示了如何LineNumberInputStream来跟踪输入行的编号。在这里，不可简单地将所有构建器都组合起来，因为必须保持LineNumberInputStream的一个句柄（注意这并非一种继承环境，所以不能简单地将in4造型到一个LineNumberInputStream）。因此，li容纳了指向LineNumberInputStream的句柄，然后在它的基础上创建一个DataInputStream，以便读入数据。</p>

  <p>这个例子也展示了如何将格式化数据写入一个文件。首先创建了一个FileOutputStream，用它同一个文件连接。考虑到效率方面的原因，它生成了一个BufferedOutputStream。这几乎肯定是我们一般的做法，但却必须明确地这样做。随后为了进行格式化，它转换成一个PrintStream。用这种方式创建的数据文件可作为一个原始的文本文件读取。</p>

  <p>标志DataInputStream何时结束的一个方法是readLine()。一旦没有更多的字串可以读取，它就会返回null。每个行都会伴随自己的行号打印到文件里。该行号可通过li查询。</p>

  <p>可看到用于out1的、一个明确指定的close()。若程序准备掉转头来，并再次读取相同的文件，这种做法就显得相当有用。然而，该程序直到结束也没有检查文件IODemo.txt。正如以前指出的那样，如果不为自己的所有输出文件调用close()，就可能发现缓冲区不会得到刷新，造成它们不完整。。</p>

  <p>10.5.2 输出流</p>

  <p>两类主要的输出流是按它们写入数据的方式划分的：一种按人的习惯写入，另一种为了以后由一个DataInputStream而写入。RandomAccessFile是独立的，尽管它的数据格式兼容于DataInputStream和DataOutputStream。</p>

  <p>5. 保存与恢复数据</p>

  <p>PrintStream能格式化数据，使其能按我们的习惯阅读。但为了输出数据，以便由另一个数据流恢复，则需用一个DataOutputStream写入数据，并用一个DataInputStream恢复（获取）数据。当然，这些数据流可以是任何东西，但这里采用的是一个文件，并进行了缓冲处理，以加快读写速度。</p>

  <p>注意字串是用writeBytes()写入的，而非writeChars()。若使用后者，写入的就是16位Unicode字符。由于DataInputStream中没有补充的“readChars”方法，所以不得不用readChar()每次取出一个字符。所以对ASCII来说，更方便的做法是将字符作为字节写入，在后面跟随一个新行；然后再用readLine()将字符当作普通的ASCII行读回。</p>

  <p>writeDouble()将double数字保存到数据流中，并用补充的readDouble()恢复它。但为了保证任何读方法能够正常工作，必须知道数据项在流中的准确位置，因为既有可能将保存的double数据作为一个简单的字节序列读入，也有可能作为char或其他格式读入。所以必须要么为文件中的数据采用固定的格式，要么将额外的信息保存到文件中，以便正确判断数据的存放位置。</p>

  <p>6. 读写随机访问文件</p>

  <p>正如早先指出的那样，RandomAccessFile与IO层次结构的剩余部分几乎是完全隔离的，尽管它也实现了DataInput和DataOutput接口。所以不可将其与InputStream及OutputStream子类的任何部分关联起来。尽管也许能将一个ByteArrayInputStream当作一个随机访问元素对待，但只能用RandomAccessFile打开一个文件。必须假定RandomAccessFile已得到了正确的缓冲，因为我们不能自行选择。</p>

  <p>可以自行选择的是第二个构建器参数：可决定以“只读”（r）方式或“读写”（rw）方式打开一个RandomAccessFile文件。</p>

  <p>使用RandomAccessFile的时候，类似于组合使用DataInputStream和DataOutputStream（因为它实现了等同的接口）。除此以外，还可看到程序中使用了seek()，以便在文件中到处移动，对某个值作出修改。</p>

  <p>10.5.3 快捷文件处理</p>

  <p>由于以前采用的一些典型形式都涉及到文件处理，所以大家也许会怀疑为什么要进行那么多的代码输入――这正是装饰器方案一个缺点。本部分将向大家展示如何创建和使用典型文件读取和写入配置的快捷版本。这些快捷版本均置入packagecom.bruceeckel.tools中（自第5章开始创建）。为了将每个类都添加到库内，只需将其置入适当的目录，并添加对应的package语句即可。</p>

  <p>7. 快速文件输入</p>

  <p>若想创建一个对象，用它从一个缓冲的DataInputStream中读取一个文件，可将这个过程封装到一个名为InFile的类内。如下所示：</p>

  <p>//: InFile.java</p>

  <p>// Shorthand class for opening an input file</p>

  <p>package com.bruceeckel.tools;</p>

  <p>import java.io.*;</p>

  <p>public class InFile extends DataInputStream {</p>

  <p>public InFile(String filename)</p>

  <p>throws FileNotFoundException {</p>

  <p>super(</p>

  <p>new BufferedInputStream(</p>

  <p>new FileInputStream(filename)));</p>

  <p>}</p>

  <p>public InFile(File file)</p>

  <p>throws FileNotFoundException {</p>

  <p>this(file.getPath());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>无论构建器的String版本还是File版本都包括在内，用于共同创建一个FileInputStream。</p>

  <p>就象这个例子展示的那样，现在可以有效减少创建文件时由于重复强调造成的问题。</p>

  <p>8. 快速输出格式化文件</p>

  <p>亦可用同类型的方法创建一个PrintStream，令其写入一个缓冲文件。下面是对com.bruceeckel.tools的扩展：</p>

  <p>//: PrintFile.java</p>

  <p>// Shorthand class for opening an output file</p>

  <p>// for human-readable output.</p>

  <p>package com.bruceeckel.tools;</p>

  <p>import java.io.*;</p>

  <p>public class PrintFile extends PrintStream {</p>

  <p>public PrintFile(String filename)</p>

  <p>throws IOException {</p>

  <p>super(</p>

  <p>new BufferedOutputStream(</p>

  <p>new FileOutputStream(filename)));</p>

  <p>}</p>

  <p>public PrintFile(File file)</p>

  <p>throws IOException {</p>

  <p>this(file.getPath());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>注意构建器不可能捕获一个由基础类构建器“掷”出的违例。</p>

  <p>9. 快速输出数据文件</p>

  <p>最后，利用类似的快捷方式可创建一个缓冲输出文件，用它保存数据（与由人观看的数据格式相反）：</p>

  <p>//: OutFile.java</p>

  <p>// Shorthand class for opening an output file</p>

  <p>// for data storage.</p>

  <p>package com.bruceeckel.tools;</p>

  <p>import java.io.*;</p>

  <p>public class OutFile extends DataOutputStream {</p>

  <p>public OutFile(String filename)</p>

  <p>throws IOException {</p>

  <p>super(</p>

  <p>new BufferedOutputStream(</p>

  <p>new FileOutputStream(filename)));</p>

  <p>}</p>

  <p>public OutFile(File file)</p>

  <p>throws IOException {</p>

  <p>this(file.getPath());</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>非常奇怪的是（也非常不幸），Java库的设计者居然没想到将这些便利措施直接作为他们的一部分标准提供。</p>

  <p>10.5.4 从标准输入中读取数据</p>

  <p>以Unix首先倡导的“标准输入”、“标准输出”以及“标准错误输出”概念为基础，Java提供了相应的System.in，System.out以及System.err。贯这一整本书，大家都会接触到如何用System.out进行标准输出，它已预封装成一个PrintStream对象。System.err同样是一个PrintStream，但Syste</p>

  <div class="mbppagebreak"></div>
</body>
</html>
